﻿//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;

using ProjectBase.Core;

namespace $safeprojectname$.Xml
{
    /// <summary>
    ///     Base DAO for entities stored in XML files.
    ///     Provides the basic simple property-attribute mapping for any entity.
    ///     Complex mapping must be implemented in derived class.
    /// </summary>
    /// <typeparam name = "TEntity"></typeparam>
    /// <typeparam name = "TID"></typeparam>
    /// <typeparam name = "TFilter"></typeparam>
    public abstract class BaseXmlDao<TEntity, TID, TFilter> : ReadXmlDao<TEntity, TID, TFilter>, IWriteDao<TEntity, TID>
        where TEntity : class, IPersistentObjectWithTypedId<TID>, ISetID<TID>
        where TFilter : class, IFilter<TID>
    {
        /// <summary>
        ///     When the base Add operation is called,
        ///     every entity ID is set to this returned value.
        ///     Derived classes must implement some type of unique ID generator
        ///     (random, increment)
        /// </summary>
        /// <returns></returns>
        protected abstract TID GetNextId(TEntity entity = null);

        /// <summary>
        ///     Mapping form the entity instance of type 'T'
        ///     into the XElement.
        ///     This basic mapping converts ValueTypes and string properties
        ///     into the XAttributes (named with the propertyName).
        ///     Converted are only NOT readOnly properties PLUS the entity.ID
        /// </summary>
        /// <param name = "entity">entity which should be converted into the XML element</param>
        /// <returns>Element with XAttributes filled with the 'entity' properties</returns>
        protected virtual XElement CreateElement(TEntity entity)
        {
            Contract.Requires(entity.Is(), " Parameter 'entity' cannot be null, when CreateElement is called. ");
            var element = new XElement(ElementName);
            element.Add(new XAttribute(Constants.Common.ID, entity.ID));

            foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(entity.GetType()))
            {
                if (!pd.IsReadOnly
                    && (pd.PropertyType.IsValueType || pd.PropertyType.Name.Equals("String"))
                    && pd.GetValue(entity).Is()
                    && !pd.Name.Equals(Constants.Common.ID))
                {
                    element.Add(new XAttribute(pd.Name, pd.GetValue(entity)));
                }
            }

            return element;
        }


        /// <summary>
        ///     Persists the changes of DataSource to the underlying XML file
        /// </summary>
        /// <param name = "dataSource"></param>
        protected virtual void Save(XDocument dataSource)
        {
            XDocuments[DataSourcePath] = dataSource;
        }

        #region IHaveMappingInfo
        static IList<MappingDescription> InitPropertyNames()
        {
            var map = new List<MappingDescription>();
            var type = typeof (TEntity);
            foreach (var property in type.GetProperties())
            {
                var mapping = new MappingDescription
                {
                    CanInsert = property.CanWrite,
                    CanUpdate = property.CanWrite,
                    PersistenceName = property.Name,
                    PropertyName = property.Name,
                    PropertyInfo = property
                };
                map.Add(mapping);
            }

            return map;
        }

        static readonly IList<MappingDescription> MappingDescriptionList = InitPropertyNames();
        public virtual IList<MappingDescription> MappingInfo
        {
            get { return MappingDescriptionList; }
        }
        #endregion IHaveMappingInfo


        #region IDao implemented SET
        /// <summary>
        ///     Basic write operation for insertingt. Stores (only new) entity.
        /// </summary>
        /// <param name = "entity">new transient entity</param>
        /// <returns>stored entity with defined identifier ID - or still transient entity if any error arises</returns>
        public virtual TEntity Add(TEntity entity)
        {
            entity.SetId(GetNextId(entity)); // set the ID for new entity
            DataSource.Root.Add(CreateElement(entity)); // append it as a node
            Save(DataSource); // save it into the underlying file
            return entity;
        }

        /// <summary>
        ///     Basic write operation for changing. Changes properties in the storage.
        /// </summary>
        /// <param name = "entity">entity which should be updated</param>
        /// <returns>updated entity - or still transient entity if any error arises</returns>
        public virtual TEntity Update(TEntity entity)
        {
            var node = GetElementById(entity.ID);
            node.ReplaceWith(CreateElement(entity));
            Save(DataSource);
            return entity;
        }

        /// <summary>
        ///     Basic write operation for deleting. Definitely removes entity from storage
        /// </summary>
        /// <param name = "entity"></param>
        public virtual void Delete(TEntity entity)
        {
            var node = GetElementById(entity.ID);
            node.Remove();
            Save(DataSource);
        }
        #endregion IDao implemented SET
    }
}